package multimedia.model.coding;

import java.util.*;

public class HuffmanEncoder
{

    
    // Variable declaration
    public static final int HUFF = 1;
    String Data;
    String eData;
    ArrayList<Byte> binaryData = new ArrayList<Byte>();// Creating the ArrayList of Bytes where
                                                       // binary data will be stored
    Hashtable<Character, Integer> fData = new Hashtable<Character, Integer>();// Creating a
                                                                              // Hashtable where
                                                                              // data will be stored
    StringBuffer code = new StringBuffer();
    TreeSet<HuffTree> tree = new TreeSet<HuffTree>(); // Creating the new Tree
    Hashtable<Character, String> HuffTable;// Declaring Hashtable that will be passed from the main
                                           // method
    String encodedData;
    Hashtable<String, Byte> bitMap = new Hashtable<String, Byte>();

    public ArrayList<Byte> encode( String Data, Hashtable<Character, String> HuffTable )
    {
        this.Data = Data;
        this.HuffTable = HuffTable;

        // this is for creating the Frequency chart and the Tree with leaves
        createFreqData();
        // displayFreqData();
        // First trees then the tree is created
        createLeaves();
        createHuffTree();
        createBitCodes(tree.first());

        System.out.println();
        // displayBitCodes();//displays the bits
        encodeToString();// encodes the bits into a String type
        buildbitMap();
        encodeStringToBits();

        return binaryData;
    }

    void createFreqData()
    {
        for ( int i = 0; i < Data.length(); i++ )
        {
            char key = Data.charAt(i);
            if ( fData.containsKey(key) )
            {
                int value = fData.get(key);
                value = value + 1;
                fData.put(key, value);
            } else
            {
                fData.put(key, 1);
            }
        }
    }

    void createLeaves()
    {
        Enumeration<Character> enumerator = fData.keys();
        while ( enumerator.hasMoreElements() )
        {
            Character nextKey = enumerator.nextElement();
            tree.add(new HuffLeaf(nextKey, fData.get(nextKey)));
        }
    }

    void createHuffTree()
    {
        while ( tree.size() > 1 )
        {
            HuffTree left = tree.first();
            tree.remove(left);

            HuffTree right = tree.first();
            tree.remove(right);

            // combines two elements into 1 element
            HuffNode tempNode = new HuffNode(left.getFrequency() + right.getFrequency(), left, right);
            tree.add(tempNode);
        }
    }

    void createBitCodes( HuffTree tree )
    {
        if ( tree instanceof HuffNode )
        {
            HuffNode node = (HuffNode) tree;

            HuffTree left = node.getLeft();
            HuffTree right = node.getRight();

            code.append("0");
            createBitCodes(left);
            code.deleteCharAt(code.length() - 1);
            code.append("1");
            createBitCodes(right);
            code.deleteCharAt(code.length() - 1);
        } else
        {
            HuffLeaf leaf = (HuffLeaf) tree;
            HuffTable.put((char) (leaf.getValue()), code.toString());
        }
    }

    void encodeToString()
    {
        StringBuffer tempEncoding = new StringBuffer();
        for ( int i = 0; i < Data.length(); i++ )
        {
            tempEncoding.append(HuffTable.get(Data.charAt(i)));
        }

        encodedData = tempEncoding.toString();
        eData = encodedData;
        System.out.println("nString Encoded Data");
    }

    public String getEncoded()
    {
        return eData;
    }

    void buildbitMap()
    {
        for ( int i = 0; i <= 255; i++ )
        {
            StringBuffer Buffer = new StringBuffer();

            if ( (i & 128) > 0 )
            {
                Buffer.append("1");
            } else
            {
                Buffer.append("0");
            }

            if ( (i & 64) > 0 )
            {
                Buffer.append("1");
            } else
            {
                Buffer.append("0");
            }

            if ( (i & 32) > 0 )
            {
                Buffer.append("1");
            } else
            {
                Buffer.append("0");
            }

            if ( (i & 16) > 0 )
            {
                Buffer.append("1");
            } else
            {
                Buffer.append("0");
            }

            if ( (i & 8) > 0 )
            {
                Buffer.append("1");
            } else
            {
                Buffer.append("0");
            }

            if ( (i & 4) > 0 )
            {
                Buffer.append("1");
            } else
            {
                Buffer.append("0");
            }

            if ( (i & 2) > 0 )
            {
                Buffer.append("1");
            } else
            {
                Buffer.append("0");
            }
            ;

            if ( (i & 1) > 0 )
            {
                Buffer.append("1");
            } else
            {
                Buffer.append("0");
            }

            bitMap.put(Buffer.toString(), (byte) (i));
        }
    }

    void encodeStringToBits()
    {
        int rem = encodedData.length() % 8;
        for ( int i = 0; i < (8 - rem); i++ )
        {
            encodedData = encodedData + "0";
        }

        for ( int i = 0; i < encodedData.length(); i += 8 )
        {
            String sBits = encodedData.substring(i, i + 8);
            byte rBits = bitMap.get(sBits);
            binaryData.add(rBits);
        }
    }

}